﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Kyoh.Lib.Text
{
    /// <summary>変更不可のマッチ文字集合を表します。</summary>
    public class Characteristic : IPattern
    {
        private readonly char[] _patternChars;
        private static readonly Regex PatternToChars = new Regex(@"(.)(?<!\\(?:\\\\)*)-(.)", RegexOptions.Compiled);
        private readonly char[] _replaceChars;

        /// <summary>指定したマッチ文字集合の <see cref="Characteristic"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるマッチ文字集合。</param>
        /// <param name="ignoreCase">文字集合の大文字小文字を区別しない場合は <c>true</c>、それ以外の場合は <c>false</c>。</param>
        public Characteristic(string pattern, bool ignoreCase = false)
        {
            _patternChars = CreateCharArray(pattern);
            IgnoreCase = ignoreCase;
            Pattern = pattern;
        }

        /// <summary>指定したマッチ文字集合の <see cref="Characteristic"/> クラスの新しいインスタンスを初期化およびコンパイルします。</summary>
        /// <param name="pattern">一致させるマッチ文字集合。</param>
        /// <param name="replaceChars">置換文字集合。</param>
        /// <param name="ignoreCase">文字集合の大文字小文字を区別しない場合は <c>true</c>、それ以外の場合は <c>false</c>。</param>
        public Characteristic(string pattern, string replaceChars, bool ignoreCase = false)
        {
            _patternChars = CreateCharArray(pattern);
            _replaceChars = CreateCharArray(replaceChars);
            IgnoreCase = ignoreCase;

            if (_replaceChars.Length == 0)
                _replaceChars = _patternChars;
            else if (_replaceChars.Length < _patternChars.Length)
            {
                var newChars = new char[_patternChars.Length];
                _replaceChars.CopyTo(newChars, 0);
                new string(_replaceChars[_replaceChars.Length-1], _patternChars.Length-_replaceChars.Length).ToCharArray().CopyTo(newChars, _replaceChars.Length);
                _replaceChars = newChars;
            }
            Pattern = pattern;
            Replacement = replaceChars;
        }

        private static char[] CreateCharArray(string pattern)
        {
            return PatternToChars.Replace(pattern, CharArrayMatchEvaluater).ToCharArray();
        }

        private static string CharArrayMatchEvaluater(Match match)
        {
            var replace = new StringBuilder();
            char charFrom = match.Groups[1].Value[0];
            char charTo = match.Groups[2].Value[0];
            int delta = Math.Sign(charTo - charFrom);
            for (int i = charFrom; i <= charTo; i += delta)
                replace.Append((char)i);
            return replace.ToString();
        }

        /// <summary>メタ文字 (-、\) をエスケープ コードに置き換えることにより、それらのメタ文字をエスケープします。</summary>
        /// <param name="str">変換対象のテキストを含んでいる入力文字列。</param>
        /// <returns>エスケープされた形に変換されたメタ文字を含んでいる文字列。</returns>
        public static string Escape(string str)
        {
            //UNDONE:
            return str;
        }

        /// <summary>入力文字列内にエスケープされた文字がある場合は、そのエスケープを解除します。</summary>
        /// <param name="str">変換対象のテキストを含んでいる入力文字列。</param>
        /// <returns>エスケープされない形式に変換された、エスケープされた文字を含む文字列。</returns>
        public static string Unescape(string str)
        {
            //UNDONE:
            return str;
        }

        /// <summary>指定されたマッチ文字集合と一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public bool IsMatch(string input)
        {
            return IsMatch(input, 0, _patternChars, IgnoreCase);
        }

        /// <summary>指定されたマッチ文字集合と一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public bool IsMatch(string input, int startat)
        {
            return IsMatch(input, startat, _patternChars, IgnoreCase);
        }

        /// <summary>指定されたマッチ文字集合と一致する対象が入力文字列内で見つかったかどうかを示します。</summary>
        public static bool IsMatch(string input, string pattern, bool ignoreCase = false)
        {
            return IsMatch(input, 0, CreateCharArray(pattern), ignoreCase);
        }

        private static bool IsMatch(string input, int startat, char[] chars, bool ignoreCase = false)
        {
            EqualityHandler equality;
            if (ignoreCase)
                equality = LikesChar;
            else
                equality = EqualsChar;

            for (int i = startat; i < input.Length; i++)
            {
                var c = input[i];
                foreach (var m in chars)
                    if (equality(c, m))
                        return true;
            }
            return false;
        }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public string Replace(string input, string replacement)
        {
            return Replace(input, 0, _patternChars, CreateCharArray(replacement), IgnoreCase);
        }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public string Replace(string input)
        {
            return Replace(input, 0, _patternChars, _replaceChars, IgnoreCase);
        }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, string replacement, bool ignoreCase = false)
        {
            return Replace(input, 0, CreateCharArray(pattern), CreateCharArray(replacement), ignoreCase);
        }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public string Replace(string input, string replacement, int startat)
        {
            return Replace(input, startat, _patternChars, CreateCharArray(replacement), IgnoreCase);
        }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public string Replace(string input, int startat)
        {
            return Replace(input, startat, _patternChars, _replaceChars, IgnoreCase);
        }

        /// <summary>マッチ文字集合を取得します。</summary>
        public string Pattern { get; private set; }

        /// <summary>置換文字集合を取得します。</summary>
        public string Replacement { get; private set; }

        /// <summary>指定した入力文字列内でマッチ文字集合に一致するすべての文字列を置換します。</summary>
        public static string Replace(string input, string pattern, string replacement, int startat, bool ignoreCase = false)
        {
            return Replace(input, startat, CreateCharArray(pattern), CreateCharArray(replacement), ignoreCase);
        }

        private static string Replace(string input, int startat, IList<char> patternChars, IList<char> replacement, bool ignoreCase = false)
        {
            EqualityHandler equality;
            if (ignoreCase)
                equality = LikesChar;
            else
                equality = EqualsChar;

            if(replacement.Count == 0)
                replacement = patternChars;
            else if (replacement.Count < patternChars.Count)
            {
            }
            var count = input.Length - startat;
            var replace = new StringBuilder(input);
            for (int i = 0; i < input.Length; i++)
            {
                for (int p = 0; p < patternChars.Count; p++)
                {
                    if (equality(replace[i], patternChars[p]))
                    {
                        replace[i] = replacement[p];
                        break;
                    }
                }
            }
            return replace.ToString();
        }

        /// <summary>コンストラクタに渡されたマッチ文字集合を返します。</summary>
        public override string ToString()
        {
            return new string(_patternChars);
        }

        private static bool EqualsChar(char c1, char c2)
        {
            return c1 == c2;
        }

        private static bool LikesChar(char c1, char c2)
        {
            return char.ToLowerInvariant(c1) == char.ToLowerInvariant(c2);
        }

        private delegate bool EqualityHandler (char c1, char c2);

        /// <summary>文字集合の大文字小文字を区別しない場合は <c>true</c>、それ以外の場合は <c>false</c>。</summary>
        public bool IgnoreCase { get; set; }
    }
}
